//! dfs 虚拟文件系统实现
//!
//! 该机制基于 linux 的 sysfs, 将系统信息或交互抽象到文件上,
//! 这样可以具有一个可视化可配置操作友好的系统交互方式.

#![feature(lazy_cell)]
#![allow(unsafe_code)]
#![allow(clippy::unused_self)]
#![allow(clippy::string_add)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::unnecessary_wraps)]

use std::any::Any;

use error::{SmkError, SmkResult};

// inode 实现
pub mod inode;

// 文件操作相关的辅助实现
mod filetable;
mod path;

// 基于 inode 的拓展文件实现
pub mod params;
pub mod seq_file;

// 文件访问的命令实现
#[cfg(feature = "command")]
pub mod command;

/// dfs 使用的返回结果
///
/// `SmkResult` 别名
pub type DfsResult<T> = SmkResult<T>;

/// dfs 错误代码
///
/// `SmkError` 别名
pub type DfsError = SmkError;

/// 打开文件选项
#[derive(Debug, Clone, Default)]
pub struct DfsOpenOptions {
    read: bool,
    write: bool,
    seek: bool,
    fcntl: bool,
    noblock: bool,
}

impl DfsOpenOptions {
    /// 构建一个打开文件选项
    pub fn new() -> Self {
        Self::default()
    }

    /// 文件描述符是否可读
    pub fn read(&mut self, read: bool) -> &mut Self {
        self.read = read;
        self
    }

    /// 文件描述符是否可写
    pub fn write(&mut self, write: bool) -> &mut Self {
        self.write = write;
        self
    }

    /// 文件描述符是否可读可写
    pub fn read_write(&mut self, rdwr: bool) -> &mut Self {
        self.read = rdwr;
        self.write = rdwr;
        self
    }

    /// 文件描述符是否可以 seek
    pub fn seek(&mut self, seek: bool) -> &mut Self {
        self.seek = seek;
        self
    }

    /// 文件描述符是否可以 fcntl
    pub fn fcntl(&mut self, fcntl: bool) -> &mut Self {
        self.fcntl = fcntl;
        self
    }

    /// 文件描述符是否可以阻塞
    pub fn noblock(&mut self, noblock: bool) -> &mut Self {
        self.noblock = noblock;
        self
    }

    /// 打开文件
    ///
    /// # Errors
    /// 文件不存在, 打开文件属性错误时将会返回错误
    pub fn open(&self, path: &str) -> DfsResult<DfsFileDesc> {
        let inode = inode::link_path_walk(path).ok_or(DfsError::NoFile)?;
        let file = inode::open(inode, self)?;
        let ret = filetable::push_file(file);
        Ok(DfsFileDesc::new(ret))
    }
}

/// 文件 Seek 操作
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DSeekFrom {
    /// 从文件开头位置
    Start(u64),
    /// 从文件结束位置
    End(i64),
    /// 从文件当前位置
    Current(i64),
}

/// 文件描述符
pub struct DfsFileDesc(u64);

impl DfsFileDesc {
    pub(crate) fn new(fd: u64) -> Self {
        Self(fd)
    }

    /// 文件描述符 id
    #[inline]
    pub fn id(&self) -> u64 {
        self.0
    }

    /// 从文件描述符读取文件数据
    ///
    /// # Errors
    /// 读取文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn read(&self, buf: &mut String) -> DfsResult<usize> {
        let file = filetable::get_file(self.0);
        let mut lock = file.lock().unwrap();
        inode::read(&mut lock, buf)
    }

    /// 向文件描述符写入文件数据
    ///
    /// # Errors
    /// 写文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn write(&self, buf: &str) -> DfsResult<usize> {
        let file = filetable::get_file(self.0);
        let mut lock = file.lock().unwrap();
        inode::write(&mut lock, buf)
    }

    /// 对文件执行 seek 操作
    ///
    /// # Errors
    /// seek 文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn seek(&self, pos: DSeekFrom) -> DfsResult<u64> {
        let file = filetable::get_file(self.0);
        let mut lock = file.lock().unwrap();
        inode::seek(&mut lock, pos)
    }

    /// 回到文件起始位置
    ///
    /// # Errors
    /// seek 文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    pub fn rewind(&self) -> DfsResult<()> {
        self.seek(DSeekFrom::Start(0))?;
        Ok(())
    }

    /// 文件大小
    ///
    /// # Errors
    /// seek 文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    pub fn stream_len(&self) -> DfsResult<u64> {
        let old_pos = self.stream_position()?;
        let len = self.seek(DSeekFrom::End(0))?;
        if old_pos != len {
            self.seek(DSeekFrom::Start(old_pos))?;
        }
        Ok(len)
    }

    /// 文件当前位置
    ///
    /// # Errors
    /// seek 文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    pub fn stream_position(&self) -> DfsResult<u64> {
        self.seek(DSeekFrom::Current(0))
    }

    /// 文件描述符 fcntl 操作
    ///
    /// # Errors
    /// fcntl 文件 mode 不匹配或者文件本身内部返回错误时最终将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn fcntl(&self, cmd: u32, data: &mut Box<dyn Any>) -> DfsResult<()> {
        let file = filetable::get_file(self.0);
        let mut lock = file.lock().unwrap();
        inode::fcntl(&mut lock, cmd, data)
    }
}

impl Drop for DfsFileDesc {
    fn drop(&mut self) {
        let file = filetable::get_file(self.0);
        let mut lock = file.lock().unwrap();
        inode::close(lock.inode.clone(), &mut lock).unwrap();
        filetable::pop_file(self.0);
    }
}

/// 快速文件读取
///
/// # Errors
/// 文件不允许读或者文件本身内部返回错误时最终将会返回错误
pub fn dfs_read(path: &str, buf: &mut String) -> DfsResult<usize> {
    let file = DfsOpenOptions::new().read(true).open(path)?;
    file.read(buf)
}

/// 快速文件写入
///
/// # Errors
/// 文件不允许写或者文件本身内部返回错误时最终将会返回错误
pub fn dfs_write(path: &str, buf: &str) -> DfsResult<usize> {
    let file = DfsOpenOptions::new().write(true).open(path)?;
    file.write(buf)
}

/// 快速文件 fcntl 操作
///
/// # Errors
/// 文件不允许 fcntl 或者文件本身内部返回错误时最终将会返回错误
pub fn dfs_fcntl(path: &str, cmd: u32, data: &mut Box<dyn Any>) -> DfsResult<()> {
    let file = DfsOpenOptions::new().fcntl(true).open(path)?;
    file.fcntl(cmd, data)
}
